#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

//#define COBS_DEBUG
#define COBS_IMPLEMENTATION
#include "cobs.h"

#include "acutest.h"

#ifndef UNUSED
#define UNUSED(X) ((void)X)
#endif

#define MAX_BUFFER 2048
static uint8_t input_buffer[MAX_BUFFER];
static uint8_t encode_buffer[MAX_BUFFER + MAX_BUFFER / 254 + 2];
static uint8_t decode_buffer[MAX_BUFFER];
static char error_string[256];

static void prefix_function(size_t bufsize)
{
    memset(input_buffer, 0, sizeof(input_buffer));
    memset(encode_buffer, 0, sizeof(input_buffer));
    memset(decode_buffer, 0, sizeof(input_buffer));
    memset(error_string, 0, sizeof(error_string));
    srand(bufsize);
    for (size_t i = 0; i < bufsize; i++)
    {
        input_buffer[i] = (uint8_t)rand();
    }
    // force at least a single zero in the buffer
    input_buffer[rand() % bufsize] = 0;
}

#ifdef COBS_DEBUG
static const char* dump_buffer(const uint8_t *buf, const size_t size)
{
    for (size_t i = 0; i < size; i++)
    {
        if (i && (i % 16 == 0))
        {
            printf("\n");
        }
        printf("%02x ", buf[i]);
    }
    printf("\n---\n");
    return "";
}
#endif //COBS_DEBUG

static void test_cobs(void)
{
    srand(0);
#ifdef COBS_DEBUG
    char *nl = "\n";
#endif //COBS_DEBUG
    for (size_t ori_size = 10; ori_size <= MAX_BUFFER; ori_size += rand() % 100 + 1)
    {
        TEST_CASE_("Message size: %lu", ori_size);
        prefix_function(ori_size);
        size_t enc_size = cobs_encode(input_buffer, ori_size, encode_buffer);
        TEST_ASSERT_(enc_size > ori_size, "the encoded buffer(%lu) is somehow not larger than the input buffer(%lu)",
            enc_size, ori_size);
        TEST_CHECK(memcmp(input_buffer, encode_buffer, ori_size));
        TEST_MSG("the encoded buffer is somehow the same as the input buffer");
        size_t dec_size = cobs_decode(encode_buffer, enc_size, decode_buffer);
#ifdef COBS_DEBUG
        printf("%s", nl); nl = "";
        dump_buffer(input_buffer, ori_size);
        dump_buffer(encode_buffer, enc_size);
        dump_buffer(decode_buffer, dec_size);
#endif //COBS_DEBUG
        printf("\n%lu->%lu->%lu", ori_size, enc_size, dec_size);
        TEST_ASSERT_(ori_size == dec_size, "the decoded buffer(%lu) is differently sized than the input buffer(%lu)",
            dec_size, ori_size);
        TEST_CHECK(!memcmp(input_buffer, decode_buffer, ori_size));
        TEST_MSG("the decoded buffer doesn't match the input buffer");
    }
    printf("\n");
}

static void test_err_handler(const char *file, const int line, const char *error_message)
{
    sprintf(error_string, "%s(%d): %s\n", file, line, error_message);
}
#define TEST_ERR_SIZE     125
#define TEST_ERR_ENC_SIZE 127
static void test_err(void)
{
    size_t enc_size, dec_size;
    cobs_error_handler = test_err_handler;

    prefix_function(TEST_ERR_SIZE);
    enc_size = cobs_encode(input_buffer, TEST_ERR_SIZE, encode_buffer);
    TEST_CHECK_(enc_size == TEST_ERR_ENC_SIZE, "enc_size = %lu", enc_size);
    TEST_CHECK_(!strstr(error_string, "both input and output data buffers must be provided"), "\"%s\"", error_string);
    enc_size = cobs_encode(NULL, TEST_ERR_SIZE, encode_buffer);
    TEST_CHECK_(enc_size == 0, "enc_size = %lu", enc_size);
    TEST_CHECK_(strstr(error_string, "both input and output data buffers must be provided"), "\"%s\"", error_string);
    enc_size = cobs_encode(input_buffer, TEST_ERR_SIZE, NULL);
    TEST_CHECK_(enc_size == 0, "enc_size = %lu", enc_size);
    TEST_CHECK_(strstr(error_string, "both input and output data buffers must be provided"), "\"%s\"", error_string);

    prefix_function(TEST_ERR_SIZE);
    enc_size = cobs_encode(input_buffer, TEST_ERR_SIZE, encode_buffer);
    dec_size = cobs_decode(encode_buffer, enc_size, decode_buffer);
    TEST_CHECK_(dec_size == TEST_ERR_SIZE, "dec_size = %lu", dec_size);
    dec_size = cobs_decode(NULL, enc_size, decode_buffer);
    TEST_CHECK_(dec_size == 0, "dec_size = %lu", dec_size);
    TEST_CHECK_(strstr(error_string, "both input and output data buffers must be provided"), "\"%s\"", error_string);
    dec_size = cobs_decode(encode_buffer, enc_size, NULL);
    TEST_CHECK_(dec_size == 0, "dec_size = %lu", dec_size);
    TEST_CHECK_(strstr(error_string, "both input and output data buffers must be provided"), "\"%s\"", error_string);

    prefix_function(TEST_ERR_SIZE);
    enc_size = cobs_encode(input_buffer, TEST_ERR_SIZE, encode_buffer);
    dec_size = cobs_decode(encode_buffer, TEST_ERR_SIZE, decode_buffer);
    TEST_CHECK_(dec_size == 0, "dec_size = %lu", dec_size);
    TEST_CHECK_(strstr(error_string, "malformed encoded data"), "\"%s\"", error_string);
}

TEST_LIST =
{
    { "encode/decode tests of many sizes", test_cobs },
    { "check error handling",              test_err  },

    { NULL, NULL },
};
